// http://en.wikipedia.org/wiki/Bzip2#Compression_stack
// TODO: pocitat s nejhorsim moznym pripadem!! pri expanzi pretece buffer!!
#include <iostream>
#include <fstream>
#include <string>
using namespace std;

#include "../config.h"

#include "../RLE/RLE.h"
#include "../BWT/BWT.h"
#include "../ModeToFront/MTF.h"
#include "../Huffman/Huffman.h"
#include "../Adler-32 checksum/Adler32.h"

int main()
{
	string file_dec;
	string file_enc;
	cout << "File to compress: ";
	cin >> file_dec;
	cout << "Compressed file: ";
	cin >> file_enc;
	//
	unsigned char *buffer = new unsigned char[Config::BLOCK_SIZE];
	ifstream fin(file_dec, ios_base::in | ios_base::binary);
	if(!fin.is_open()) { cerr << "error" << endl; return 0; }
	ofstream fout(file_enc, ios_base::out | ios_base::binary);
	if(!fout.is_open()) { cerr << "error" << endl; return 0; }
	//
	streamsize read;
	unsigned int bwt_dec_start, enc_len, checksum;
	unsigned char *encoded = new unsigned char[Config::BLOCK_SIZE];
	unsigned char *decoded = new unsigned char[Config::BLOCK_SIZE];
	unsigned short *rle_ab_enc = new unsigned short[Config::BLOCK_SIZE];
	fout.write("PRC", 3);
	while(1)
	{
		fin.read((char *)buffer, Config::BLOCK_SIZE);
		if((read = fin.gcount()) > 0)
		{
			RLE::Encode_MN5(buffer, read, encoded, enc_len);
			BWT::Encode(enc_len, encoded, buffer, bwt_dec_start);
			MTF::Encode(buffer, enc_len, encoded+4);
			((int *)encoded)[0] = bwt_dec_start; enc_len += 4;
			RLE::Encode_AB(encoded, enc_len, rle_ab_enc, enc_len);
			Huffman::Encode(enc_len, rle_ab_enc, encoded, enc_len);
			checksum = Adler32::Compute(encoded, enc_len);
			// write out
			fout.write((char *)&enc_len, sizeof(unsigned int));	// encoded block length (for faster decompression - especially in parallel)
			fout.write((char *)encoded, enc_len);				// encoded block
			fout.write((char *)&checksum, sizeof(unsigned int));// checksum of the encoded block
		}
		if(fin.eof()) break;
	}
	fout.close();
	fin.close();
	//
	delete [] buffer;
	delete [] encoded;
	delete [] decoded;
	delete [] rle_ab_enc;
	//
	return 0;
}